use std::path::{Path, PathBuf};

use libloading::Library;

use common_base::dashmap::{mapref::one::Ref, DashMap};
use common_base::prelude::*;

#[derive(Debug)]
pub struct DylibRepository {
    dylib_root_dir: PathBuf,
    dylibs: DashMap<String, Library>,
}

impl DylibRepository {
    pub fn new(dylib_root_dir: &str) -> Self {
        Self {
            dylib_root_dir: PathBuf::from(dylib_root_dir),
            dylibs: DashMap::default(),
        }
    }
}

impl DylibRepository {
    pub fn get_or_create(&self, dylib_name: &str) -> Result<Ref<String, Library>> {
        if let Some(dylib) = self.dylibs.get(dylib_name) {
            return Ok(dylib);
        }

        info!(
            "create dylib {} in root dir {:?}.",
            dylib_name, self.dylib_root_dir
        );
        self.dylibs.insert(
            String::from(dylib_name),
            Self::create(self.dylib_root_dir.as_path(), dylib_name)?,
        );
        info!("create dylib {} success.", dylib_name,);

        Ok(self.dylibs.get(dylib_name).unwrap())
    }

    fn create(dylib_root_dir: &Path, dylib_name: &str) -> Result<Library> {
        let mut dylib_path = PathBuf::from(dylib_root_dir);
        dylib_path.push(dylib_name);
        if cfg!(target_os = "windows") {
            dylib_path.set_extension("dll");
        } else {
            dylib_path.set_extension("so");
        }

        if !dylib_path.exists() {
            return Err(ErrorCode::DylibLoadFailed(format!(
                "can not find dylib: {:?}.",
                dylib_path
            )));
        }

        unsafe { Ok(Library::new(dylib_path)?) }
    }
}
